{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 词嵌入\n",
    "前面讲了循环神经网络做简单的图像分类问题和飞机流量时序预测，但是现在循环神经网络最火热的应用是自然语言处理，下面我们介绍一下自然语言处理中如果运用循环神经网络，首先我们介绍一下第一个概念，词嵌入。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "对于图像分类问题，我们可以使用 one-hot 的类型去编码，比如一共有 5 类，那么属于第二类就可以用 (0, 1, 0, 0, 0) 去表示，对于分类问题，这样当然忒别简单，但是在自然语言处理中，因为单词的数目过多，这样做就行不通了，比如有 10000 个不同的词，那么使用 one-hot 不仅效率低，同时还没有办法表达出单词的特点，这个时候就引入了词嵌入去表达每一个单词。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "词向量简单来说就是用一个向量去表示一个词语，但是这个向量并不是随机的，因为这样并没有任何意义，所以我们需要对每个词有一个特定的向量去表示他们，而有一些词的词性是相近的，比如”(love)喜欢”和”(like)爱”，对于这种词性相近的词，我们需要他们的向量表示也能够相近，如何去度量和定义向量之间的相近呢？非常简单，就是使用两个向量的夹角，夹角越小，越相近，这样就有了一个完备的定义。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "我们举一个例子，下面有 4 段话\n",
    "\n",
    "1. The cat likes playing wool.\n",
    "\n",
    "2. The kitty likes playing wool.\n",
    "\n",
    "3. The dog likes playing ball.\n",
    "\n",
    "4. The boy does not like playing ball or wool.\n",
    "\n",
    "这里面有 4 个词，分别是 cat, kitty, dog 和 boy。下面我们使用一个二维的词向量 (a, b) 来表示每一个词，其中 a，b 分别代表着这个词的一种属性，比如 a 代表是否喜欢玩球，b 代表是否喜欢玩毛线，数值越大表示越喜欢，那么我们就能够用数值来定义每一个单词。\n",
    "\n",
    "对于 cat，我们可以定义它的词嵌入为 (-1, 4)，因为他不喜欢玩球，喜欢玩毛线，同时可以定义 kitty 为 (-2, 5)，dog 为 (3, 2) 以及 boy 为 (-2, -3)，那么把这四个向量在坐标系中表示出来，就是\n",
    "\n",
    "<img src=\"https://ws1.sinaimg.cn/large/006tNc79gy1fmwf2jxhbzj30g40b2my2.jpg\" width=\"350\">"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "可以看到，上面这张图就显示了不同词嵌入之间的夹角，kitty 和 cat 之间的夹角比较小，所以他们更相似，dog 和 boy 之间的夹角很大，所以他们是不相似的。\n",
    "\n",
    "下面我们看看 pytorch 中如何调用词向量"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## PyTorch 实现\n",
    "词嵌入在 pytorch 中非常简单，只需要调用 `torch.nn.Embedding(m, n)` 就可以了，m 表示单词的总数目，n 表示词嵌入的维度，其实词嵌入就相当于是一个大矩阵，矩阵的每一行表示一个单词"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "import torch\n",
    "from torch import nn\n",
    "from torch.autograd import Variable"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# 定义词嵌入\n",
    "embeds = nn.Embedding(2, 5) # 2 个单词，维度 5"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Parameter containing:\n",
       "-1.3426  0.7316 -0.2437  0.4925 -0.0191\n",
       "-0.8326  0.3367  0.2135  0.5059  0.8326\n",
       "[torch.FloatTensor of size 2x5]"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 得到词嵌入矩阵\n",
    "embeds.weight"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "我们通过 `weight` 得到了整个词嵌入的矩阵，注意，这个矩阵是一个可以改变的 parameter，在网络的训练中会不断更新，同时词嵌入的数值可以直接进行修改，比如我们可以读入一个预训练好的词嵌入等等"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Parameter containing:\n",
       " 1  1  1  1  1\n",
       " 1  1  1  1  1\n",
       "[torch.FloatTensor of size 2x5]"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 直接手动修改词嵌入的值\n",
    "embeds.weight.data = torch.ones(2, 5)\n",
    "embeds.weight"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# 访问第 50 个词的词向量\n",
    "embeds = nn.Embedding(100, 10)\n",
    "single_word_embed = embeds(Variable(torch.LongTensor([50])))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Variable containing:\n",
       "-1.4954 -1.8475  0.2913 -0.9674 -2.1250 -0.5783 -0.6717  0.5638  0.7038  0.4437\n",
       "[torch.FloatTensor of size 1x10]"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "single_word_embed"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "可以看到如果我们要访问其中一个单词的词向量，我们可以直接调用定义好的词嵌入，但是输入必须传入一个 Variable，且类型是 LongTensor"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "虽然我们知道了如何定义词向量的相似性，但是我们仍然不知道如何得到词嵌入，因为如果一个词嵌入式 100 维，这显然不可能人为去赋值，所以为了得到词向量，需要介绍 skip-gram 模型。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Skip-Gram 模型\n",
    "Skip Gram 模型是 [Word2Vec](https://arxiv.org/pdf/1301.3781.pdf) 这篇论文的网络架构，下面我们来讲一讲这个模型。\n",
    "\n",
    "## 模型结构\n",
    "skip-gram 模型非常简单，我们在一段文本中训练一个简单的网络，这个网络的任务是通过一个词周围的词来预测这个词，然而我们实际上要做的就是训练我们的词嵌入。\n",
    "\n",
    "比如我们给定一句话中的一个词，看看它周围的词，然后随机挑选一个，我们希望网络能够输出一个概率值，这个概率值能够告诉我们到底这个词离我们选择的词的远近程度，比如这么一句话 'A dog is playing with a ball'，如果我们选的词是 'ball'，那么 'playing' 就要比 'dog' 离我们选择的词更近。\n",
    "\n",
    "对于一段话，我们可以按照顺序选择不同的词，然后构建训练样本和 label，比如\n",
    "\n",
    "![](https://ws2.sinaimg.cn/large/006tNc79gy1fmwlpfp3loj30hh0ah75l.jpg)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "对于这个例子，我们依次取一个词以及其周围的词构成一个训练样本，比如第一次选择的词是 'the'，那么我们取其前后两个词作为训练样本，这个也可以被称为一个滑动窗口，对于第一个词，其左边没有单词，所以训练集就是三个词，然后我们在这三个词中选择 'the' 作为输入，另外两个词都是他的输出，就构成了两个训练样本，又比如选择 'fox' 这个词，那么加上其左边两个词，右边两个词，一共是 5 个词，然后选择 'fox' 作为输入，那么输出就是其周围的四个词，一共可以构成 4 个训练样本，通过这个办法，我们就能够训练出需要的词嵌入。\n",
    "\n",
    "下次课，我们会讲一讲词嵌入到底有什么用。"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
